// 一些自定义的基本业务逻辑
#include <arpa/inet.h>
#include <string>

#include "my_memory.h"
#include "my_lock.h"
#include "net/slogic.h"
#include "net/socket.h"
#include "net/crc32.h"
#include "net/netglobal.h"
#include "net/logiccomm.h"
#include "log.h"

// 定义本类的成员函数指针（用于存储处理函数数组）
// 第一个参数指向连接池中的一个连接
// 第二个参数是消息头指针
// 第三个参数是包体的指针
// 第四个参数是包体长度
typedef bool (CLogicSocket::*handler)(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength);

// 保存成员指针的数组（数组中的是处理函数）
static const handler statusHandler[] = {
    // 数组前5个元素，保留，以备将来增加一些基本服务器功能
    &CLogicSocket::_HandlePing, // 第0个是心跳包
    NULL,                       // 保留
    NULL,                       // 保留
    NULL,                       // 保留
    NULL,                       // 保留
    // 下面开始才是具体业务
    //&CLogicSocket::_HandleRegister,// 实现祖册功能
    //&CLogicSocket::_HandleLogIn,// 实现登录功能
    &CLogicSocket::_HandleLogic, // 5实现转发收到的客户端信息给业务逻辑功能
    &CLogicSocket::_HandlePress, // 6压力测试回复
    // ......
};

// 宏定义整个处理函数个数
#define AUTH_TOTAL_COMMANDS sizeof(statusHandler) / sizeof(handler)

// 构造函数
CLogicSocket::CLogicSocket() {}

// 析构函数
CLogicSocket::~CLogicSocket() {}

// 初始化函数（在创建子进程前调用）
bool CLogicSocket::Initialize()
{
    bool bParentInit = CSocket::Initialize(); // 调用基类初始化函数(读配置与开启监听)
    return bParentInit;
}

// 重写基类的函数
// 处理收到的数据包，由线程池来调用本函数，本函数是一个单独的线程；
void CLogicSocket::ThreadRecvProcFunc(char *pMsgBuf)
{
    LPSTRUC_MSG_HEADER pMsgHeader = (LPSTRUC_MSG_HEADER)pMsgBuf;                   // 获取消息头
    LPCOMM_PKG_HEADER pPkgHeader = (LPCOMM_PKG_HEADER)(pMsgBuf + m_iLenMsgHeader); // 获取包头
    void *pPkgBody;                                                                // 指向包体的指针
    unsigned short pkglen = ntohs(pPkgHeader->pkgLen);                             // 包长度（包头+包体）

    // 检查收到的包的CRC校验值
    // 如果没有包体，只有包头
    if (m_iLenPkgHeader == pkglen)
    {
        // 这种包的crc值为0，不匹配直接丢弃
        if (pPkgHeader->crc32 != 0)
        {
            return;
        }
        pPkgBody = NULL;
    }
    // 如果有包体，检查校验值是否正确
    else
    {
        pPkgHeader->crc32 = ntohl(pPkgHeader->crc32);                     // 转为主机序
        pPkgBody = (void *)(pMsgBuf + m_iLenMsgHeader + m_iLenPkgHeader); // 跳过消息头、包头，指向包体

        // 计算校验和
        int calccrc = CCRC32::GetInstance()->Get_CRC((unsigned char *)pPkgBody, pkglen - m_iLenPkgHeader);
        if (calccrc != pPkgHeader->crc32)
        { // 自己计算的和包中的进行比较
            Log_Error(0, "CLogicSocket::threadRecvProcFunc()中CRC错误[服务器:%d/客户端:%d]，丢弃数据!", calccrc, pPkgHeader->crc32);
            return;
        }
    }

    // 通过校验和，检查消息要求调用的处理函数是否存在
    unsigned short imsgCode = ntohs(pPkgHeader->msgCode); // 取出消息类型
    lpconnection_t p_Conn = pMsgHeader->pConn;            // 取出对应的连接池连接

    // 如果这个连接过期了
    if (p_Conn->iCurrsequence != pMsgHeader->iCurrsequence)
    {
        return; // 可能是客户端断开了
    }

    // 如果消息中要求调用的函数编号不对，也丢弃
    if (imsgCode >= AUTH_TOTAL_COMMANDS)
    {
        Log_Error(0, "CLogicSocket::threadRecvProcFunc()中imsgCode=%d消息码不对!", imsgCode);
        return; // 可能是错误包或恶意包
    }

    // 如果消息中要求调用的函数在数组中找不到
    if (statusHandler[imsgCode] == NULL)
    {
        Log_Error(0, "CLogicSocket::threadRecvProcFunc()中imsgCode=%d消息码找不到对应的处理函数!", imsgCode);
        return; // 可能是错误包或恶意包
    }

    // 到这说明有相关的处理函数，直接调用
    (this->*statusHandler[imsgCode])(p_Conn, pMsgHeader, (char *)pPkgBody, pkglen - m_iLenPkgHeader);
}

// 心跳包检测时间到，该去检测心跳包是否超时的事宜，子类继承重写
void CLogicSocket::ProcPingTimeOutChecking(LPSTRUC_MSG_HEADER tmpmsg, time_t cur_time)
{
    CMemory *p_memory = CMemory::GetInstance();

    // 如果连接还没过期（还没断开）
    if (tmpmsg->iCurrsequence == tmpmsg->pConn->iCurrsequence)
    {
        lpconnection_t p_Conn = tmpmsg->pConn;
        // 如果开启了立即踢人
        if (m_ifTimeOutKick == 1)
        {
            ZdClosesocketProc(p_Conn); // 踢出
        }
        // 超时踢的判断标准就是 每次检查的时间间隔*3，超过这个时间没发送心跳包，就踢
        else if ((cur_time - p_Conn->lastPingTime) > (m_iWaitTime * 3 + 10))
        {
            // 如果此时该用户正好断线，则这个socket可能立即被后续上来的连接复用，就会踢错，不过不处理，算他倒霉
            ZdClosesocketProc(p_Conn); // 踢出
        }
        p_memory->FreeMemory(tmpmsg); // 释放内存
    }
    // 如果已经断了
    else
    {
        p_memory->FreeMemory(tmpmsg); // 释放内存
    }
}

// 发送没有包体的数据包给客户端（通用的）
void CLogicSocket::SendNoBodyPkgToClient(LPSTRUC_MSG_HEADER pMsgHeader, unsigned short iMsgCode)
{
    CMemory *p_memory = CMemory::GetInstance();
    char *p_sendbuf = (char *)p_memory->AllocMemory(m_iLenMsgHeader + m_iLenPkgHeader, false);
    char *p_tmpbuf = p_sendbuf; // 指向消息头起始位置

    memcpy(p_tmpbuf, pMsgHeader, m_iLenMsgHeader); // 重置消息头部分
    p_tmpbuf += m_iLenMsgHeader;                   // 移动到包头起始位置

    LPCOMM_PKG_HEADER pPkgHeader = (LPCOMM_PKG_HEADER)p_tmpbuf;
    pPkgHeader->msgCode = htons(iMsgCode);       // 消息类型（指明调用的处理函数编号）
    pPkgHeader->pkgLen = htons(m_iLenPkgHeader); // 设置长度
    pPkgHeader->crc32 = 0;                       // 没有包体，校验和是0
    MsgSend(p_sendbuf);                          // 发送消息
}

// 发送来自业务进程的数据包给客户端（存入发送队列）
void CLogicSocket::SendLogicPkgToClient(std::string data, int fd)
{
    // 1.先尝试查找这个连接池对象，找不到直接退出
    lpconnection_t p_Conn = Get_connectionByFd(fd); // 调用父类函数查找连接池对象
    if (!p_Conn)
        return;

    // 2.准备打包消息
    LPCOMM_PKG_HEADER pPkgHeader;
    CMemory *p_memory = CMemory::GetInstance();
    CCRC32 *p_crc32 = CCRC32::GetInstance();
    int iSendLen = data.size();

    char *p_sendbuf = (char *)p_memory->AllocMemory(m_iLenMsgHeader + m_iLenPkgHeader + iSendLen, false);
    char *p_tmpbuf = p_sendbuf; // 指向消息头起始位置
    // 处理消息头
    LPSTRUC_MSG_HEADER pMsgHeader = (LPSTRUC_MSG_HEADER)p_tmpbuf;
    pMsgHeader->pConn = p_Conn;
    pMsgHeader->iCurrsequence = p_Conn->iCurrsequence;
    memcpy(p_tmpbuf, pMsgHeader, m_iLenMsgHeader);
    // 处理包头
    pPkgHeader = (LPCOMM_PKG_HEADER)(p_sendbuf + m_iLenMsgHeader);
    pPkgHeader->msgCode = htons(_CMD_LOGIC); // 消息类型（指明调用的处理函数编号）
    pPkgHeader->pkgLen = htons(m_iLenPkgHeader + iSendLen);
    // 填充包体
    char *p_body = p_sendbuf + m_iLenMsgHeader + m_iLenPkgHeader; // 指向包体开始
    memcpy(p_body, data.c_str(), iSendLen);
    pPkgHeader->crc32 = p_crc32->Get_CRC((unsigned char *)p_body, iSendLen); // 计算校验和
    pPkgHeader->crc32 = htonl(pPkgHeader->crc32);

    MsgSend(p_sendbuf); // 发送消息
}

//---------------------业务处理函数------------------------------
/**
 * 接收心跳包函数
 * @param pConn 消息的连接池对象
 * @param pMsgHeader 消息头指针
 * @param pPkgBody 包体指针
 * @param iBodyLength 包体长度
 */
bool CLogicSocket::_HandlePing(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength)
{
    // 有包体则认为是非法包
    if (iBodyLength != 0)
    {
        return false;
    }

    // 凡是和当前用户有关的访问都考虑用互斥，避免用户同时发送过来两个命令达到各种作弊目的
    CLock lock(&pConn->logicPorcMutex);
    pConn->lastPingTime = time(NULL); // 更新上一次收到心跳包的时间

    // 服务器也发送给客户端一个只有包头的数据包作为返回的数据
    SendNoBodyPkgToClient(pMsgHeader, _CMD_PING);

    // Log_Error(0,"成功收到了心跳包并返回结果！");
    return true;
}

/**
 * 收到客户端消息转发到业务进程的函数
 * @param pConn 消息的连接池对象
 * @param pMsgHeader 消息头指针
 * @param pPkgBody 包体指针
 * @param iBodyLength 包体长度
 */
bool CLogicSocket::_HandleLogic(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength)
{
    // 步骤1：判断包体的合法性（自定义协商约定）
    if (pPkgBody == NULL || iBodyLength == 0)
    {
        return false;
    }

    // 步骤2：凡是和当前用户有关的访问都考虑用互斥，避免用户同时发送过来两个命令达到各种作弊目的
    CLock lock(&pConn->logicPorcMutex);

    // 步骤3：同时发给master进程和game进程（自行判断是不是自己的）
    int ret1 = g_zmq0.SendMsg(pPkgBody, iBodyLength);
    int ret2 = g_zmq1.SendMsg(pPkgBody, iBodyLength);
    if (ret1 == -1 || ret2 == -1)
    {
        Log_Info(0, "警告, net模块有一个来自客户端的消息转发给业务进程失败!!"); // 打印日志警告，不影响程序运行
        return false;
    }
    return true;
}

bool CLogicSocket::_HandlePress(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength)
{
    // 空包才被接受
    SendNoBodyPkgToClient(pMsgHeader, 6); // 构建一个空包并回复
    return true;
}

// 已弃用，供参考
// // 注册函数
// bool CLogicSocket::_HandleRegister(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength)
// {
//     // 步骤1：判断包体的合法性（自定义协商约定）
//     if(pPkgBody == NULL){
//         return false;
//     }
//     // 发送过来的结构大小不对，认为是恶意包，直接不处理
//     int iRecvLen = sizeof(STRUCT_REGISTER);
//     if(iRecvLen != iBodyLength){
//         return false;
//     }

//     // 步骤2：凡是和当前用户有关的访问都考虑用互斥，避免用户同时发送过来两个命令达到各种作弊目的
//     CLock lock(&pConn->logicPorcMutex);

//     // 步骤3：取得整个发送过来的数据
//     LPSTRUCT_REGISTER p_RecvInfo = (LPSTRUCT_REGISTER)pPkgBody;
//     p_RecvInfo->iType = ntohl(p_RecvInfo->iType);// 类型，收到后网络转主机序
//     // 这非常关键，给内容尾部添加终结符，防止收到畸形包导致服务器直接使用出错
//     p_RecvInfo->username[sizeof(p_RecvInfo->username) - 1] = 0;
//     p_RecvInfo->password[sizeof(p_RecvInfo->password) - 1] = 0;

//     // 步骤4：这里可以根据业务逻辑，进一步判断收到的数据的合法性，比如检查用户当前该玩家的状态是否适合收到这个数据等等

//     // 步骤5：给客户端返回数据，这里我们就以给客户端也返回同样的 STRUCT_REGISTER 结构来举例
// 	LPCOMM_PKG_HEADER pPkgHeader;
// 	CMemory  *p_memory = CMemory::GetInstance();
// 	CCRC32   *p_crc32 = CCRC32::GetInstance();
//     int iSendLen = sizeof(STRUCT_REGISTER);

//     // 5-1：分配要发送出去的包的内存
//     char *p_sendbuf = (char *)p_memory->AllocMemory(m_iLenMsgHeader+m_iLenPkgHeader+iSendLen,false);//准备发送的格式，这里是 消息头+包头+包体

//     // 5-2：填充消息头
//     memcpy(p_sendbuf,pMsgHeader, m_iLenMsgHeader);

//     // 5-3：填充包头
//     pPkgHeader = (LPCOMM_PKG_HEADER)(p_sendbuf+m_iLenMsgHeader);    //指向包头
//     pPkgHeader->msgCode = _CMD_REGISTER;	                        //消息代码，可以统一在logiccomm.h中定义
//     pPkgHeader->msgCode = htons(pPkgHeader->msgCode);	            //htons主机序转网络序
//     pPkgHeader->pkgLen  = htons(m_iLenPkgHeader + iSendLen);        //整个包的尺寸【包头+包体尺寸】
//     // 5-4：填充包体
//     LPSTRUCT_REGISTER p_sendInfo = (LPSTRUCT_REGISTER)(p_sendbuf+m_iLenMsgHeader+m_iLenPkgHeader);
//     //...根据需要填充要发回给客户端的内容,int类型要使用htonl()转，short类型要使用htons()转；

//     // 5-5：包体内容全部确定好后，计算包体的crc32值
//     pPkgHeader->crc32   = p_crc32->Get_CRC((unsigned char *)p_sendInfo,iSendLen);
//     pPkgHeader->crc32   = htonl(pPkgHeader->crc32);

//     // 5-6：发送数据包
//     MsgSend(p_sendbuf);
//     return true;
// }

// // 登录函数
// bool CLogicSocket::_HandleLogIn(lpconnection_t pConn, LPSTRUC_MSG_HEADER pMsgHeader, char *pPkgBody, unsigned short iBodyLength)
// {
//     // 步骤1：判断包体的合法性（自定义协商约定）
//     if(pPkgBody == NULL){
//         return false;
//     }
//     // 发送过来的结构大小不对，认为是恶意包，直接不处理
//     int iRecvLen = sizeof(STRUCT_LOGIN);
//     if(iRecvLen != iBodyLength){
//         return false;
//     }

//     // 步骤2：凡是和当前用户有关的访问都考虑用互斥，避免用户同时发送过来两个命令达到各种作弊目的
//     CLock lock(&pConn->logicPorcMutex);

//     // 步骤3：取得整个发送过来的数据
//     LPSTRUCT_LOGIN p_RecvInfo = (LPSTRUCT_LOGIN)pPkgBody;
//     // 这非常关键，给内容尾部添加终结符，防止收到畸形包导致服务器直接使用出错
//     p_RecvInfo->username[sizeof(p_RecvInfo->username) -1 ] = 0;
//     p_RecvInfo->password[sizeof(p_RecvInfo->password) -1 ] = 0;

//     // 步骤4：这里可以根据业务逻辑，进一步判断收到的数据的合法性，比如检查用户当前该玩家的状态是否适合收到这个数据等等

//     // 步骤5：给客户端返回数据，这里我们就以给客户端也返回同样的 STRUCT_REGISTER 结构来举例
// 	LPCOMM_PKG_HEADER pPkgHeader;
// 	CMemory  *p_memory = CMemory::GetInstance();
// 	CCRC32   *p_crc32 = CCRC32::GetInstance();

//     // 5-1：分配要发送出去的包的内存
//     int iSendLen = sizeof(STRUCT_LOGIN);
//     char *p_sendbuf = (char *)p_memory->AllocMemory(m_iLenMsgHeader+m_iLenPkgHeader+iSendLen,false);

//     // 5-2：填充消息头
//     memcpy(p_sendbuf,pMsgHeader,m_iLenMsgHeader);

//     // 5-3：填充包头
//     pPkgHeader = (LPCOMM_PKG_HEADER)(p_sendbuf+m_iLenMsgHeader);
//     pPkgHeader->msgCode = _CMD_LOGIN;
//     pPkgHeader->msgCode = htons(pPkgHeader->msgCode);
//     pPkgHeader->pkgLen  = htons(m_iLenPkgHeader + iSendLen);

//     // 5-4：填充包体
//     LPSTRUCT_LOGIN p_sendInfo = (LPSTRUCT_LOGIN)(p_sendbuf+m_iLenMsgHeader+m_iLenPkgHeader);
//     //...根据需要填充要发回给客户端的内容,int类型要使用htonl()转，short类型要使用htons()转；

//     // 5-5：包体内容全部确定好后，计算包体的crc32值
//     pPkgHeader->crc32   = p_crc32->Get_CRC((unsigned char *)p_sendInfo,iSendLen);
//     pPkgHeader->crc32   = htonl(pPkgHeader->crc32);

//     // 5-6：发送数据包
//     MsgSend(p_sendbuf);
//     return true;
// }
